/*
 *  trace-recorder.cpp
 *  joosVM
 *
 *  Created by Smári Waage on 26.9.2009.
 *  Copyright 2009 smari.org. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. *
 */

//#include "trace-recorder.h"

#if TRACE_JIT 

#include "interpreter.h"
#include <stdio.h>
#include <stdlib.h>

static SideExit* FASTCALL callTree(mem_slot* state, Fragment* f, JoosSideExit* treeGuard)
{
    //union { NIns *code; GuardRecord* (FASTCALL *func)(void*, Fragment*); } u;
	code2Func u;
    u.code = f->code();
	
	
    GuardRecord* rec;
	TraceTreeObject* tto = (TraceTreeObject*)f->vmprivate;
	mem_slot a[tto->getCallSize()];
	
	int startidx = treeGuard->storeStartIdx;
	for(int i=0;i<treeGuard->storeSize;i++){
		mem_slot v = state[i+startidx];
		a[i] = v;
		//ddout("%i\n",v)
	}
	
	//make a pointer to the start of the vpointers for the fragment to use.
	Vtable* vt = (Vtable*)tto->getVTable()->address()+StaticArray::kArrayOffset;
	rec = u.func(a,vt);
	
	JoosSideExit* lr = (JoosSideExit*)rec->exit;
	if(lr->exitType == LoopExit){
		for(int i=0;i<treeGuard->storeSize;i++){
			mem_slot v = a[i];
			state[i+startidx] = v;
			//ddout("%i\n",v)			
		}
	}else{
		//it is not practical to embed this in the exit because the 
		//size of it can change after the tree call is created, e.g. with a branch extension that inlines a method.
		mem_slot *b = new mem_slot[tto->getCallSize()];
		for(int i=0;i<tto->getCallSize();i++){
			b[i] = a[i];
		}
		//have to remember to kill-it in the restore.
		treeGuard->nestStore = b;
	}
	
	treeGuard->nest = lr;
	
	return lr;
}
MAKE_CALLINFO(callTree,ARGSIZE_I,3,ABI_FASTCALL);


JSTraceMonitor* TraceContext::getTraceMonitor(){
	return getTraceContext()->traceMonitor;
}

TraceContext* TraceContext::inst = NULL;
TraceContext* TraceContext::getTraceContext(){
	if(inst == NULL){
		inst = new TraceContext();
	}
	return inst;
}
TraceContext::TraceContext(){
	traceMonitor = new JSTraceMonitor;
	TraceJIT::InitJIT(traceMonitor);
}

TraceContext::~TraceContext(){
	delete traceMonitor;
}


Level::Level(JMethod* _method, Level* _previous, JoosSideExit* _levelExit, TraceRecorder* _recorder):
stackSize(method->getMaxStackSize()),storeSize(method->getMaxLocals()),method(_method),previous(_previous),levelExit(_levelExit),recorder(_recorder){
	
	if(previous){
		levelpos = previous->levelpos +1;
		storeStartIdx = previous->storeStartIdx + previous->storeSize;
	}else{
		levelpos = 0;
		storeStartIdx = 0;
	}
	
	//recursion check
	Level* lv = previous;
	for(int i=0;kInlineLevelMax>=i&&lv!=NULL;i++){
		JASSERT(method != lv->method)
		lv = lv->previous;
	}
	
	stack = new LIns*[stackSize];
	stackPos = 0;
	store = new StoreInfo[storeSize];
	clearStore();

	
}
Level::~Level(){
	delete stack;
	delete store;
}

void Level::push(LIns* v){
	JASSERT(stackPos < stackSize);
	stack[stackPos++] = v;
}

LIns* Level::pop(){
	JASSERT(stackPos > 0);
	return stack[--stackPos];
}

LIns* Level::getStoreSlot(int i){
	
	if(store[i].current == 0){
		int idx = i + storeStartIdx;
		LIns* read = recorder->lir->insLoad(LIR_ld,recorder->fragment->lirbuf->state, mem_slotIndex(idx));
		store[i].current = read;
	}
	return store[i].current;
}

void Level::putStoreSlot(int i, LIns* value){
	store[i].current = value;
	store[i].write = true;
}

void Level::commitStore(){
	//commit the store to memmory.
	for(int i=0; i< storeSize; i++){
		if( store[i].write ){
			recorder->lir->insStorei( store[i].current ,recorder->fragment->lirbuf->state,mem_slotIndex((i+storeStartIdx)));
		}
	}
}

void Level::clearStore(){
	//zero out the store, so it will force a read from memory.
	memset(store, 0, sizeof(StoreInfo)*storeSize);	
}

JoosSideExit* Level::createSideExit(ExitType type){
	
	//commit store
	commitStore();
	
	JoosSideExit* exit = (JoosSideExit*)recorder->traceMonitor->traceAlloc->alloc(sizeof(JoosSideExit));	
	exit->from = recorder->fragment;
	exit->exitType = type;
	exit->next = levelExit;
	
	exit->levelpos = levelpos;
	exit->method = recorder->insertPointerGetIndex(method); 
	exit->storeStartIdx = storeStartIdx;
	exit->storeSize = storeSize;	
	exit->stackPos = stackPos;
	
	if(stackPos > 0){
		exit->stack = (mem_slot*)recorder->traceMonitor->traceAlloc->alloc( stackPos * sizeof(mem_slot));
		
		for(int i=0; i<stackPos;i++){
			//int idx = sizeof(JoosSideExit) + mem_slotIndex(i);
			//ddout("recorder restore stack pos: %p\n",exit->stack);
			recorder->lir->insStorei( stack[i] ,recorder->lir->insImmPtr(exit->stack),mem_slotIndex(i));
			//LIns* args[] = {stack[i]};
			//recorder->lir->insCall(&printout_ci, args);
		}
	}
	return exit;
}


TraceRecorder* TraceRecorder::current = NULL;
TraceRecorder::TraceRecorder(JMethod* method, int startpc, int endpc, TraceTreeObject* tto)
	:_anchor(NULL), ttree(tto),vtablepos(0){
		
		traceMonitor = TraceContext::getTraceMonitor();
		if(tto->getFragment() == NULL){
			fragment = new (*traceMonitor->dataAlloc) Fragment(tto);//"this" is only to have some address
			fragment->root = fragment;
			fragment->lirbuf = traceMonitor->lirbuf;
			fragment->vmprivate = ttree;
			ttree->setFragment(fragment);
		}else{
			fragment = tto->getFragment();
		}
		ttree->setStartPc(startpc);
		ttree->setEndPc(endpc);
		
		initRecorder(method);
}

TraceRecorder::TraceRecorder( JMethod* method, JoosSideExit* anchor): _anchor(anchor),loophead(NULL){
	
	JASSERT(anchor->exitType == WrongBranch)
	
	traceMonitor = TraceContext::getTraceMonitor();
	fragment = new (*traceMonitor->dataAlloc) Fragment(this);//"this" is only to have some address
	fragment->root = _anchor->from->root;
	fragment->lirbuf = traceMonitor->lirbuf;

	fragment->vmprivate = fragment->root->vmprivate;
	ttree = (TraceTreeObject*)fragment->vmprivate;
	
	//Count the pointers in the vtable.
	StaticArray* vt = ttree->getVTable();
	vtablepos = 0;
	int len = vt->array_length();
	Pointer* p;
	while ( vtablepos < len && (p = vt->get(vtablepos))!= NULL){
		vtablepos++;
	}
		
	initRecorder(method);	
}


TraceRecorder::~TraceRecorder(){
	delete level;
	//delete inInfo;
	//delete _store;
	//delete lir;
}

bool	TraceRecorder::isBlackList(){
	return ttree->getState() == BlackList;
}
void	TraceRecorder::blacklist(){
	cancelRecord();
	ttree->setState(BlackList);
}

LIns* TraceRecorder::insertPointer(HeapObject* obj){
	
	StaticArray* vt = ttree->getVTable();
	//if the pointer is in the table just return the index
	
	for(int i=0;i<vt->array_length();i++){//read max 2, 0 slots.
		if(vt->get(i) == obj){
			if(pointerStore[i] == NULL){
				pointerStore[i] = lir->insLoad(LIR_ld,fragment->lirbuf->param1, mem_slotIndex(i));
			}
			return pointerStore[i];
		}
	}
	
	//its not in the table then need to add it.
	if(vt->array_length() <= vtablepos){
		LIns **pointerStoretmp = new LIns*[vt->array_length() +2];
		memset(pointerStoretmp, 0, (vt->array_length()+2) *4);
		memcpy(pointerStoretmp, pointerStore, vt->array_length()*4);
		delete pointerStore;
		pointerStore = pointerStoretmp;
		
		//if there is not enough space, resize the table.
		vt = vt->resizeRelocate(2);
		ttree->setVTable(vt);
		
		dout(debug_trace,"Resize table %p\n",vt);
	}
	//save it in the table.
	vt->set(vtablepos, obj);
	dout(debug_trace,"Add pointer %p slot %i\n",obj,vtablepos);
	//TODO: treat like store to reduce readings.
	
	 pointerStore[vtablepos] = lir->insLoad(LIR_ld,fragment->lirbuf->param1, mem_slotIndex(vtablepos));
	return pointerStore[vtablepos++];
}

int TraceRecorder::insertPointerGetIndex(HeapObject* obj){
	
	StaticArray* vt = ttree->getVTable();
	//if the pointer is in the table just return the index
	
	for(int i=0;i<vt->array_length();i++){//read max 2, 0 slots.
		if(vt->get(i) == obj){
			return i;
		}
	}
	
	//its not in the table then need to add it.
	if(vt->array_length() <= vtablepos){
		LIns **pointerStoretmp = new LIns*[vt->array_length() +2];
		memset(pointerStoretmp, 0, (vt->array_length()+2) *4);
		memcpy(pointerStoretmp, pointerStore, vt->array_length()*4);
		delete pointerStore;
		pointerStore = pointerStoretmp;
		
		//if there is not enough space, resize the table.
		vt = vt->resizeRelocate(2);
		ttree->setVTable(vt);
		
		dout(debug_trace,"Resize table %p\n",vt);
	}
	int r = vtablepos;
	vtablepos++;
	//save it in the table.
	vt->set(r, obj);
	dout(debug_trace,"Add pointer %p slot %i\n",obj,vtablepos);
	//TODO: treat like store to reduce readings.
	
	pointerStore[r] = lir->insLoad(LIR_ld,fragment->lirbuf->param1, mem_slotIndex(r));
	return r;
}


void TraceRecorder::initRecorder(JMethod* method){
	
	lir = new (*traceMonitor->tempAlloc) LirBufWriter(fragment->lirbuf);
	
	//lir =   new (*traceMonitor->tempAlloc) CseFilter(lir, *traceMonitor->tempAlloc);
    //lir =	new (*traceMonitor->tempAlloc) ExprFilter(lir);
   
	
	//Start new level implement
	level = new Level(method,NULL,NULL,this);
	storeTotalSize = level->storeSize;
	treeCallCount = 0;
	
	pointerStore = new LIns*[ttree->getVTable()->array_length()];
	memset(pointerStore, 0, ttree->getVTable()->array_length() *4);
	
	//todo:something else
	current = this;
	ttree->setState(Recording);
	
	prelog();
}

void TraceRecorder::resetRecorder(JMethod* method){
	dout(debug_trace,"RESET Recorder\n");
	if(fragment->code() != NULL){ddout("Trying to reset a compiled fragment");exit(1);}
	JASSERT(!_anchor);

	//delete inInfo;
	//delete _store;

	fragment->lirbuf->clear();
	//delete lir;
	
	initRecorder(method);
	dout(debug_trace,"Restart recording for fragment:%p\n",fragment);
}

void TraceRecorder::startRecord(){
	Flags::recordTrace = true;
	dout(debug_trace_short,"Start recording\n");
}

void TraceRecorder::closeRecord(){
	Flags::recordTrace = false;
	if(isBlackList()){
		Level* l = level;
		while(l->levelpos >0) l= l->previous;
		TraceTreeObject* tto = (TraceTreeObject*)fragment->vmprivate;
		l->method->blacklist(tto->getStartPc());
	}else {
		epilog();
		compile();
	}
	dout(debug_trace_short,"Compiled fragment\n");
}


void TraceRecorder::cancelRecord(){
	Flags::recordTrace = false;
	fragment->lirbuf->clear();
	lir = NULL;
	//delete lir;
	//cancel recording, we have a finite amount of attempts before we just giveup.
	dout(debug_trace,"Cancel recording:%p\n",fragment);
	ttree->setState(Canceled);
	dout(debug_trace_short,"Cancel recording\n");
}

void TraceRecorder::prelog(){
	
	lir->ins0(LIR_start);
	
	//pretty important to remember to include the registers
	for (int i = 0; i < NumSavedRegs; ++i)
		lir->insParam(i, 1);
	
	//The first parameter, memory block, secound pointer vtable.
	fragment->lirbuf->state = lir->insParam(0,0);
	fragment->lirbuf->param1 = lir->insParam(1,0);
	
	
	//If it is the root fragment then we jump just by a label.
	if(!_anchor)
		loophead = lir->ins0(LIR_label);	
}

void TraceRecorder::epilog(){
	JoosSideExit* exit = createJoosSideExit(ShouldNeverGetHere);
	
	if(loophead){
		//if this is the root fragment we just loop back to the top via label.
		lir->insBranch(LIR_j, NULL, loophead);
	}else if(_anchor){
		//set the anchor to exit to this fragment.
		_anchor->target = fragment;
		
		//this is a fragment so the exit will be directed to the top of the root fragment.
		exit->target = _anchor->from->root;// fragment->root;
		exit->exitType = Loop;
		
	}else{
		fprintf(stderr, "Trying to close something that is not a loop nor an anchor!!\n");
		std::exit(1);
	}
	
	fragment->lastIns = lir->insGuard(LIR_x,NULL, createGuardRecord(exit));
	//just to be on the safe side make it explicit that state is alive.
	lir->ins1(LIR_live,fragment->lirbuf->state);
	lir->ins1(LIR_live,fragment->lirbuf->param1);
}


void TraceRecorder::compile(){
	//make ready to be called

	Assembler *assm = traceMonitor->assembler;
    nanojit::compile(assm, fragment, *traceMonitor->tempAlloc);
	
	if (assm->error() != nanojit::None) {
		fprintf(stderr, "error compiling fragment\n");
		std::exit(1);
	}
	
	//if we are extending the tree, then I need to patch the exit of the anchor so it will find the new fragment nexttime it exits.
	if(_anchor && !loophead)
		assm->patch(_anchor);
	
	TraceTreeObject* tto = (TraceTreeObject*)fragment->vmprivate;
	tto->setCallSize(storeTotalSize);
	ttree->setState(Compiled);
	
	dout(debug_trace,"Compiled fragment:%p with root:%p\n",fragment,fragment->root);
}


GuardRecord* TraceRecorder::createGuardRecord(JoosSideExit* exit){
    GuardRecord* gr = new (*traceMonitor->traceAlloc) GuardRecord();
    gr->exit = exit;
    exit->addGuard(gr);
	
    return gr;
}

JoosSideExit* TraceRecorder::createJoosSideExit(ExitType exitType){
	
	JoosSideExit* exit = level->createSideExit(exitType);
	return exit;
}


void TraceRecorder::commitStore(){
	level->commitStore();
}

void TraceRecorder::clearStore(){
	level->clearStore();
	//zero out the store, so it will get read from memory.
}


void TraceRecorder::push(LIns* v){
	level->push(v);
}

LIns* TraceRecorder::pop(){
	return level->pop();	
}

int checkInvocationType(JMethod* method, Instance* type){
	//printf("%p %p\n",method->getContainingType(), type->getType());
	return method->getContainingType() == type->getType();
}
MAKE_CALLINFO(checkInvocationType,ARGSIZE_I,2,ABI_STDCALL);

void TraceRecorder::pushLevel(JMethod* method, int paramCount, bool _static, int pc){
	dout(debug_trace," -being inlined level:%i method:%p inline\n",level->levelpos+1,method);
	
	if(level->levelpos >= kInlineLevelMax){
		blacklist();
	}
	
	//pop off the paramenters
	JoosSideExit* typeexit;
	
	if(!_static)
		typeexit = createJoosSideExit(WrongBranch);
	
	LIns* params[paramCount];
	for(int i=paramCount-1;i>=0;i--)
		params[i] = pop();
	
	if(!_static){
		//insert typeguard
		LIns* argss[] = {pop(), insertPointer(method)};
		LIns* res = lir->insCall(&checkInvocationType_ci, argss);
		
		//JoosSideExit* exit = (JoosSideExit*)traceMonitor->traceAlloc->alloc(sizeof(JoosSideExit));	
		typeexit->pc = pc - 3; //place the pc before the invocation.
		lir->insGuard(LIR_xt,lir->ins_eq0(res), createGuardRecord(typeexit));
		
		//guard(true, res ,WrongType);
	}
	//here is the level guard needed.
	JoosSideExit* exit = createJoosSideExit(LevelGuard);
	exit->pc = pc;
	level = new Level(method,level,exit,this);
	//level makes a recursion check, and max level.
	storeTotalSize += level->storeSize;
	
	//insert the parameters
	for(int i=0; i<paramCount;i++)
		putStoreSlot(i, params[i]);
	
		
}

void TraceRecorder::restoreLevel(JMethod* method, JoosSideExit* levelExit){
	
	level = new Level(method,level, levelExit,this);
	storeTotalSize += level->storeSize;
}

void TraceRecorder::popLevel(bool returnWithValue){
	JASSERT(level->levelpos >0)
	
	dout(debug_trace,"Pop off level:%i \n",level->levelpos)
	
	LIns* ret;
	if(returnWithValue)
		ret = pop();
	
	Level* oldlevel = level;
	level = level->previous;
	
	if(returnWithValue)
		push(ret);
	
	//tidy up
	delete oldlevel;
	oldlevel = NULL;
}

LIns* TraceRecorder::getStoreSlot(int i){
	return level->getStoreSlot(i);
}

void TraceRecorder::putStoreSlot(int i, LIns* value){
	level->putStoreSlot(i, value);
}
void TraceRecorder::guard(bool expected, LIns* cond, int pc, int jump){
	
    if (!cond->isCond()) {
        expected = !expected;
        cond = lir->ins_eq0(cond);
    }
	
	
	//check for cold do while loop, that could be a problem.
	JoosSideExit* exit;
	int endPc = ((TraceTreeObject*)fragment->vmprivate)->getEndPc();
	if(level->levelpos == 0 && jump + (pc-3) == endPc){
		
		//loop exit
		exit = createJoosSideExit(LoopExit);
		exit->pc = endPc;
		
		if(expected){
			dout(debug_trace_short,"Short circuit loop\n");
			cancelRecord();
			//closeRecord();
			//fragment->lastIns = lir->insGuard(LIR_x,cond, createGuardRecord(exit));
			//compile();
			return;
		}
	}else{
		//wrongbranch, make space for rebuild information.
		exit = createJoosSideExit(WrongBranch);
		//59-27-3 = 35 .. = 59-3+27 = 83 = 83-3+-21 = 59
		
		//fix the pc to restore right position
		exit->pc = expected?pc-(jump-3):(pc-3)+jump;
	}
	
	lir->insGuard(expected ? LIR_xf : LIR_xt, cond, createGuardRecord(exit));
	//just to be safe
	//lir->ins1(LIR_live,fragment->lirbuf->state);
	
	dout(debug_trace,"\tcreate %s pc:%i rest-pc:%i Exp:%s jmp:%i Addr:%p StackSize:%i\n",endPc == jump+(pc-3)?"LoopGuard":"BranchGuard",pc,exit->pc,expected?"true":"false",jump,exit,exit->stackPos);
}

JoosSideExit* TraceRecorder::guard(bool expected, LIns* cond, ExitType exitType){
	
    if (!cond->isCond()) {
        expected = !expected;
        cond = lir->ins_eq0(cond);
    }
	
	JoosSideExit* exit;// = (JoosSideExit*)traceMonitor->traceAlloc->alloc(sizeof(JoosSideExit));
	//exit->exitType = exitType;
	
	exit = createJoosSideExit(exitType);
	lir->insGuard(expected ? LIR_xf : LIR_xt, cond, createGuardRecord(exit));
	
	return exit;
	
	dout(debug_trace,"\tcreate SpecialGuard:%i Addr:%p \n", exitType, exit);
}

void TraceRecorder::makeTreeCall(Fragment* f){
	
	JASSERT(treeCallCount++ < kTreeCallMax && fragment != f &&  f->isRoot())
	dout(debug_trace,"\nFragment:%p make a call to a tree:%p treeCallcount:%i\n",fragment,f->root,treeCallCount)

	TraceTreeObject* tto = (TraceTreeObject*)f->vmprivate;
	
	JoosSideExit* exit = createJoosSideExit(NestedExit);
	exit->pc = tto->getEndPc();
	dout(debug_trace,"\tcreate NestingGuard:%p %i %i\n",exit,exit->levelpos,exit->storeStartIdx)
	//here I need to call the treefragment with a new list.
	// also to align to the right begining.
	// then afterwards restore to other list.
	
	LIns* args[] = {lir->insImmPtr(exit), lir->insImmPtr(f), fragment->lirbuf->state }; 
	LIns* ret = lir->insCall(&callTree_ci, args);
	
	//read from the nested loop if the return was good
	LIns* retValue = lir->insLoad(LIR_ld,ret,offsetof(JoosSideExit,exitType));
	
	//else break and ...todo: forward the breaking guard to the top. 
	lir->insGuard(LIR_xf, lir->ins2i(LIR_eq, retValue, 0), createGuardRecord(exit));
	
	// we commited before we went in so just invalidate the information in the LIns store, so it will be read agin,
	clearStore();	
	
}


#endif



